/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.scheduler.specific.sge.qbase;

import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import javax.xml.datatype.XMLGregorianCalendar;

import jp.aist.gtrc.plus.scheduler.job.JobStateType;
import jp.aist.gtrc.plus.scheduler.specific.sge.jaxb.common.*;
import jp.aist.gtrc.plus.scheduler.specific.sge.jaxb.qstat.HardRequest;
import jp.aist.gtrc.plus.scheduler.specific.sge.jaxb.qstat.JAXBQSTATJob;
import jp.aist.gtrc.plus.scheduler.specific.sge.jaxb.qstat.JAXBQSTATQinstance;
import jp.aist.gtrc.plus.scheduler.specific.sge.status.SGEJobStatus;
import jp.aist.gtrc.plus.scheduler.specific.sge.status.SGEQInstance;
import jp.aist.gtrc.plus.scheduler.specific.sge.status.SGETaskStatus;
import jp.aist.gtrc.plus.scheduler.util.CollectionUtil;
import jp.aist.gtrc.plus.scheduler.util.TextUtil;
import jp.aist.gtrc.plus.scheduler.util.TimeUtil;

class SGEJAXBObjectBuilder {

	/*
	 * NOTE: use common.ObjectFactory, not qstat.ObjectFactory!
	 */
	private static final ObjectFactory factory = new ObjectFactory();

	JAXBSGEServer createServer(String name) {

		JAXBSGEServer server = factory.createJAXBSGEServer();
		server.setAHName(name);
		return server;

	}

	JAXBSGEQueue createQueue(String name,
			HashMap<String, Collection<JAXBSGEQinstance>> qinsMap) {

		JAXBSGEQueue queue = factory.createJAXBSGEQueue();
		queue.setCQName(name);

		if (qinsMap != null) {
			// NOTE: we assume key of qinsMap is qname such as "all.q"
			Collection<JAXBSGEQinstance> qinsList = qinsMap.get(name);
			if (qinsList != null) {
				CQQinstances ins = factory.createCQQinstances();
				ins.getQUElement().addAll(qinsList);
				queue.setCQQinstances(ins);
			} else {
				assert(false);
				// qinsList must exist, if qinsMap exists!
			}
		}

		return queue;

	}

	JAXBSGEQinstance createQinstance(String line) {

		/*
		 * +++ qstat -f output of Normal SGE +++
		 * queuename                      qtype used/tot. load_avg arch          states
		 * ---------------------------------------------------------------------------------
		 * all.q@hostA.example.com        BIP   0/4       0.16     lx26-x86
		 *
		 * +++ qstat -f output of SGE with Advance-Reservation (SGE 6.1AR) +++
		 * queuename                      qtype resv/used/tot. load_avg arch          states
		 * ---------------------------------------------------------------------------------
		 * all.q@hostA.example.com        BIP   0/0/4          0.16     lx26-x86
		 */
		String[] params = line.split("\\s+");
		if ((params.length != 5) && (params.length != 6)) {
			return null;
		}
		if (params[2].endsWith("tot.")) {
			return null;
		}

		String[] n = params[2].split("/");
		if ((n.length < 2) || (n.length > 3)) {
			assert(false);
			return null;
		}
		int slots;
		try {
			int totalIdx = n.length -1;
			slots = Integer.parseInt( n[totalIdx] );
		} catch (NumberFormatException e) {
			assert(false);
			return null;
		}
		String[] name = params[0].split("@");
		String state = (params.length == 6) ? params[5] : null;

		JAXBSGEQinstance ins = factory.createJAXBSGEQinstance();
		ins.setQUFullName(params[0]);
		ins.setQUJobSlots(slots);
		ins.setQUQhostname(name[1]);
		ins.setQUQname(name[0]);
		ins.setQUState(SGEQInstance.getStateValue(state));
		return ins;

	}

	private HLElement createHLElement(String name, String value, int sec) {

		HLElement e = factory.createHLElement();
		e.setHLName(name);
		e.setHLValue(value);
		e.setHLLastUpdate(sec);
		return e;

	}

	private CEElement createCEElement(String name, String value, boolean consumable) {

		CEElement e = factory.createCEElement();
		e.setCEName(name);
		e.setCEStringval(value);
		e.setCEConsumable(consumable);
		// TODO not enough?
		return e;

	}

	private double toDouble(String value) {

		if (TextUtil.isValid(value)) {
			try {
				return Double.parseDouble(value);
			} catch (NumberFormatException e) {
				// ignore, fall-through
			}
		}
		return 0.0;

	}

	private double toDouble(HLElement e) {

		return (e != null) ? toDouble(e.getHLValue()) : 0.0;

	}

	JAXBSGENode createNode(String hostname, LinkedList<String> attrs) {

		JAXBSGENode jaxbNode = factory.createJAXBSGENode();
		jaxbNode.setEHName(hostname);

		HashMap<String, HLElement> loadMap = new HashMap<String, HLElement>();
		int now = TimeUtil.calToSec(Calendar.getInstance());

		for (String line : attrs) {
			String[] val = line.split(":|=");
			if (val.length != 3) {
				continue;
			}
			if (val[0].endsWith("hl")) {
				HLElement e = createHLElement(val[1], val[2], now);
				loadMap.put(e.getHLName(), e);
			} else if (val[0].endsWith("hc")) {
				EHConsumableConfigList cl = jaxbNode.getEHConsumableConfigList();
				if (cl == null) {
					cl = factory.createEHConsumableConfigList();
					jaxbNode.setEHConsumableConfigList(cl);
				}
				List<CEElement> confList = cl.getCEElement();
				confList.add(createCEElement(val[1], val[2], true));
			}
		}

		if (loadMap.size() > 0) {
			jaxbNode.setEHLoadList(factory.createEHLoadList());
			List<HLElement> loadList = jaxbNode.getEHLoadList().getHLElement();
			loadList.addAll(loadMap.values());

			jaxbNode.setEHProcessors((int)toDouble(loadMap.get("num_proc")));
		}

		return jaxbNode;

	}

	private RNElement createRNElement(int min, int max, int step) {

		RNElement e = factory.createRNElement();
		e.setRNMin(min);
		e.setRNMax(max);
		e.setRNStep(step);
		return e;

	}

	private RNElement createRNElement(String range) {

		if (range == null) {
			// not multi tasks job, only one task job
			return createRNElement(1, 1, 1);
		}

		// ex: "1", "1-10", "1-10:2" etc
		String[] id = range.split("-|:");
		int min, max, step;
		try {
			switch (id.length) {
			case 3:
				min = Integer.parseInt(id[0]);
				max = Integer.parseInt(id[1]);
				step = Integer.parseInt(id[2]);
				break;
			case 2:
				min = Integer.parseInt(id[0]);
				max = Integer.parseInt(id[1]);
				step = 1;
				break;
			case 1:
				min = Integer.parseInt(id[0]);
				max = min;
				step = 1;
				break;
			default:
				return null;
			}
		} catch (NumberFormatException e) {
			return null;
		}

		return createRNElement(min, max, step);

	}

	private JGElement createJGElement(JAXBQSTATQinstance qins) {

		assert(qins != null);

		JGElement e = factory.createJGElement();
		String[] name = qins.getName().split("@");
		e.setJGQname(name[0]);
		e.setJGQhostname(name[1]);
		e.setJGSlots(qins.getSlotTotal());
		return e;

	}

	private JATGrantedDestinIdentifierList createGrantedList(
			JAXBQSTATQinstance qins) {

		if (qins != null) {
			JATGrantedDestinIdentifierList list
				= factory.createJATGrantedDestinIdentifierList();
			List<JGElement> jgList = list.getJGElement();
			jgList.add(createJGElement(qins));
			return list;
		} else {
			return null;
		}

	}

	private JAXBSGETask createTask(JAXBQSTATJob qstatJob, int tid,
			JAXBQSTATQinstance qins) {

		JAXBSGETask jaxbTask = factory.createJAXBSGETask();
		jaxbTask.setJATTaskNumber(tid);
		XMLGregorianCalendar start = qstatJob.getJATStartTime();
		if (start != null) {
			jaxbTask.setJATStartTime(
					TimeUtil.calToSec(start.toGregorianCalendar()));
		}
		jaxbTask.setJATStatus(
				SGETaskStatus.getSGEStateValue(qstatJob.getState()));
		jaxbTask.setJATGrantedDestinIdentifierList(
				createGrantedList(qins));

		/*
		jaxbTask.setJATEndTime();
		jaxbTask.setJATJobRestarted();
		jaxbTask.setJATMasterQueue();
		jaxbTask.setJATUsageList();
		*/

		return jaxbTask;

	}

	private JBHardResourceList createHardResList(JAXBQSTATJob qstatJob) {

		List<HardRequest> hardReqs = qstatJob.getHardRequest();
		if (CollectionUtil.isEmpty(hardReqs)) {
			return null;
		}

		JBHardResourceList resList = factory.createJBHardResourceList();
		List<CEElement> ceList = resList.getQstatLRequests();
		for (HardRequest req : hardReqs) {
			CEElement e = createCEElement(req.getName(), req.getValue(), false);
			ceList.add(e);
		}
		return resList;

	}

	private QRElement createQRElement(String qname) {

		QRElement e = factory.createQRElement();
		e.setQRName(qname);
		return e;

	}

	private JBHardQueueList createHardQueueList(JAXBQSTATJob qstatJob) {

		Collection<String> reqQnames = qstatJob.getHardReqQueue();
		if (CollectionUtil.isEmpty(reqQnames)) {
			return null;
		}

		JBHardQueueList qList = factory.createJBHardQueueList();
		List<QRElement> qrList = qList.getDestinIdentList();
		for (String name : reqQnames) {
			qrList.add(createQRElement(name));
		}
		return qList;

	}

	JAXBSGEJob createJob(JAXBQSTATJob qstatJob, JAXBQSTATQinstance qins) {

		JAXBSGEJob jaxbJob = factory.createJAXBSGEJob();

		jaxbJob.setJBJobNumber(qstatJob.getJBJobNumber());
		Integer prio = qstatJob.getJBPriority();
		if (prio != null) {
			jaxbJob.setJBPriority((int)prio);
		}
		jaxbJob.setJBJobName(qstatJob.getJBName());
		jaxbJob.setJBOwner(qstatJob.getJBOwner());

		XMLGregorianCalendar submit = qstatJob.getJBSubmissionTime();
		if (submit != null) {
			jaxbJob.setJBSubmissionTime(
					TimeUtil.calToSec(submit.toGregorianCalendar()));
		}

		// handle task id range
		JBJaStructure ja = factory.createJBJaStructure();
		RNElement tidRange = createRNElement(qstatJob.getTasks());
		ja.setTaskIdRange(tidRange);
		jaxbJob.setJBJaStructure(ja);

		// make template task
		JBJaTasks tempTasks = factory.createJBJaTasks();
		tempTasks.getUlongSublist().add(createTask(qstatJob, 1, qins));
		jaxbJob.setJBJaTemplate(tempTasks);

		// set tid range, make running task information
		JobStateType state = SGEJobStatus.getState(qstatJob.getState());
		switch (state) {
		case Queued:
			jaxbJob.setJBJaNHIds(ja);
			break;
		case Running:
			JBJaTasks runTasks = factory.createJBJaTasks();
			JAXBSGETask task = createTask(qstatJob,
					tidRange.getRNMin(), qins);
			runTasks.getUlongSublist().add(task);
			jaxbJob.setJBJaTasks(runTasks);
			break;
		case Exiting:
			break;
		case Suspended:
			break;
		case Held:
			jaxbJob.setJBJaUHIds(ja);
			break;
		default:
			assert(false);
			return null;
		}

		JBHardResourceList resList = createHardResList(qstatJob);
		if (resList != null) {
			jaxbJob.setJBHardResourceList(resList);
		}

		JBHardQueueList qList = createHardQueueList(qstatJob);
		if (qList != null) {
			jaxbJob.setJBHardQueueList(qList);
		}

		/*
		jaxbJob.setJBContext();
		jaxbJob.setJBDeadline();
		jaxbJob.setJBExecutionTime();
		jaxbJob.setJBGid();
		jaxbJob.setJBGroup();
		jaxbJob.setJBJaNHIds();
		jaxbJob.setJBJaOHIds();
		jaxbJob.setJBJaSHIds();
		jaxbJob.setJBJaUHIds();
		jaxbJob.setJBJaUHIds();
		jaxbJob.setJBPe();
		jaxbJob.setJBPeRange();
		jaxbJob.setJBProject();
		jaxbJob.setJBReserve();
		jaxbJob.setJBRestart();
		jaxbJob.setJBUid();
		*/

		return jaxbJob;

	}

}
